home *** CD-ROM | disk | FTP | other *** search
/ MacHack 1993 / MacHack 1993.toast / MacHack™ 1987-1992 / MacHack™ '92 / Other stuff / Cool Debugging Tools / Leaks dcmd / Leaks.p < prev    next >
Encoding:
Text File  |  1990-10-24  |  55.4 KB  |  1,147 lines  |  [TEXT/MPS ]

  1. { Leaks:
  2.     a dcmd to display potential memory leaks.
  3.     
  4.     Copyright © 1990 by Apple Computer, Inc., all rights reserved.
  5.  
  6.     by Bo3b Johnson    x4625        10/9/90
  7.     
  8.     for best viewing, use Palatino 12.
  9. }
  10.  
  11. UNIT Leaks;
  12.  
  13. (* 
  14.         Build this dude using the Build Menu, it has a make file.
  15.  
  16.         
  17.         This dcmd is a leak detector, intended to help you find memory leaks from programs
  18.         that are orphaning handles or pointers in the heap.  It is a non-deterministic problem
  19.         to try to find leaks, so I do a funky thing:  You have to run the operation you are 
  20.         checking 3 times.  Then, this dcmd will look for 3 blocks of the same size, allocated
  21.         from the same code, and will display a stack crawl for that purported leak.  Needless
  22.         to say, this is not bulletproof.  The human running this command is expected to check
  23.         things out further to see if it is a real leak or not.  
  24.  
  25.         I do this weirdness by patching NewHandle/DisposHandle, NewPtr/DisposPtr.
  26.         I watch pointers/handles going by when they are allocated and disposed, and save their
  27.         addresses off in a b-tree inside a big-ass block in the system heap.   I use a b-tree so the
  28.         machine has no perceptible loss of speed even though I've patched several often used traps.
  29.         When a DisposX goes by, I mark the address off my list, since it was validly disposed.  
  30.         When this dcmd comes in, it looks through the b-tree for entries still in it, and 
  31.         dumps out info about each element that is still in the tree; with the constraints that the 
  32.         block size has to be the same for three or more blocks, and they have to have the same
  33.         stack crawl.
  34.         
  35.         One dangerous aspect of this code is that most of it is recursive.  The reason of course is
  36.         that I use a b-tree to track the information about each of the blocks I see go by.  The use
  37.         of a b-tree is the only way to do this because otherwise the system will slow down
  38.         radically if this code were to use something lame like a linear list.  This way, there is 
  39.         no appreciable hit on the speed of the computer, even if I am tracking several thousand
  40.         blocks.  This is hip.  The problem of course is that b-trees lend themselves really, really
  41.         nicely to recursive routines to drive the data structure.  With Macsbug having a gutless
  42.         1K stack, this is of course fairly dangerous.  I thus go out of my way to make sure that
  43.         I pass the minimum number of parameters during a recursive operation.  This is
  44.         typically a 4 byte pointer to an element.  It is not strictly a requirement that I have to
  45.         do it recursively, it just makes the code smaller and easier to understand.  
  46.         
  47.         One thing that isn't clear, is whether local strings, used for display, will burn up stack
  48.         space.  I think they don't but if they do, they could maybe be made global instead.  It is
  49.         also unclear whether globals subtract from stack space as well, or whether you get 1K
  50.         of stack, regardless of the number of globals.  I've mostly presumed that is true, that
  51.         there is a fixed stack, after globals are allocated.  With that in mind, I've minimized
  52.         all the local variables used by the routines, as well as the parameters passed in.  This
  53.         has resulted in a number of globals, but there's always a tradeoff.
  54.         
  55.         Stack operations are alleviated a little bit since the dcmd can have global variables.
  56.         This makes it possible to avoid having to pass in some parameters that are the same
  57.         each time, at the expense of being less maintainable.  This code is pretty small though,
  58.         so it's worth it.
  59.  
  60.         Since this is a dcmd, I try to avoid using the toolbox as much as possible.  This means
  61.         avoiding things like StringtoNum, and UprCase, even though I could have used some    
  62.         of these things.  Any use of the toolbox is probably bad, since I can't be sure the heap is in
  63.         a consistent shape when dumping info.  The exception to this, is that at startup I want
  64.         to allocate a big block to store the records (elements) that save info about each block seen.
  65.         After that first allocation, I avoid using the toolbox as much as possible.  In fact, as far
  66.         as I know at this point, I don't use any toolbox calls, except during the init of the dcmd,
  67.         and when the tree is being dumped I use RsrcMapEntry to determine if some handle
  68.         I've got is a resource or not.   This won't allocate memory.
  69.  
  70.         Macsbug calls the dcmd at Init time, which is when Macsbug is loaded, early in boot.  
  71.         At that point, I create and save the buffer that is used to store the b-tree records that
  72.         track each block allocated in the system.  Since I have global variables, I use one of
  73.         those to save off the address of the block created, so I can get back to it at will.  This
  74.         global is saved in the dcmd apparently, which is great, since it makes it possible to
  75.         get back to the block without having to do something sick, like patch Chain which    
  76.         is what I was doing.  A marked improvement.
  77.         
  78.         I'm using RsrcMapEntry to drive the resource map for me, trying to match an address
  79.         to a possible resource.  If the handle in question is a resource, then it cannot be a leak
  80.         since the resource manager is still using the handle.  This allows me to avoid a few
  81.         false alarms, for blocks that are the same size, and stack crawl, allocated from the 
  82.         resource manager.  This is also a danger zone in this code.  I cannot guarantee that
  83.         the routine won't get called at interrupt time, and if it is, then the rsrcmaphandle
  84.         may not be correct when this guy tries to use it.  How big a problem is this?  It is 
  85.         something to note anyway.
  86.         
  87.         Another thing it should probably do is to watch for applications going away and mark
  88.         all the blocks in their heap out of the list.  Right now if you launch an app twice in a row
  89.         with it turned on, you'll get a b-tree fried error, which is caused because a block with
  90.         the same address is being added to the tree, and this should never happen for blocks
  91.         that are actively in use.  The b-tree check routine notices that blocks got added to the
  92.         list, and will flag it.  You only get the error message when the tree status is checked,
  93.         which is any time it is invoked in Macsbug.
  94.         
  95.         The analysis routine is seriously way slow when it has a big tree to check, since it is
  96.         an N-squared problem.  It is likely that there is a more optimal way, but I wanted 
  97.         something sooner instead of later.
  98.  
  99.         Notably this is a sick thing.  This whole dcmd is a heurstic way of finding memory 
  100.         leaks, and as such it may not work properly in all cases.  I am very interested to know
  101.         of cases where it fails, either by being too strict and reporting false leaks, and also where
  102.         it might be filtering too much data, and not showing a leak when there actually is one.
  103.         If you see any of these cases, let me know, and I'll try to fix it.  The problem of course is
  104.         that the Macintosh memory management is pretty funky and it is unlikely that there
  105.         is a completely solid way of doing this type of function.  Is this why it hasn't been done
  106.         before?  Probably.  As an example of the problem, think about trying to differentiate 
  107.         between a persistent block (something allocated early on in an application, like CODE 1)
  108.         and a genuine leak.  The CODE 1 handle will be around for ever, but it is not a leak, since
  109.         it is not multiplying.  Now, how do you find a block that is allocated during an application's
  110.         init time that actually is a leak, but only happens once?  How can you tell the difference?
  111.         Notably, I can't see that case here either (but don't really care, since it loses a chunk of
  112.         memory that is wasted, but won't crash things long term).
  113.         I believe the system runs in a very heuristic fashion, so the tools must do so as well.
  114.         This tool should still be quite useful, even though it is not 100% solid.  This is the
  115.         often maligned 90% solution.   I'm betting you'll like it better than the nonexistent
  116.         100% solution.
  117.         
  118.         I implement a memory based b-tree, to watch memory manager blocks.  
  119.         This is done in pascal so as to make it easier to maintain.  
  120.         The basic idea here is that this is the code to manage the big block in low memory
  121.         as a set of records (TrackingTableEntry), where each entry is part of a b-tree.  If a 
  122.         a record is not in use, it is in a linked list of empty records, starting with pEmptyQ
  123.         as the pointer to the first one.  The pTreeTop is a pointer to the first TrackingTableEntry,
  124.         which is the head of a b-tree of these records.  Each record keeps track of a single
  125.         memory manager block in any heap in the system, via the address field.  This code
  126.         isolates the b-tree management stuff from the skanky assembly junk required to
  127.         patch the traps effectively. 
  128.         
  129.         Note that this isn't the most mondo b-tree code ever made.  It is a very simple, easy
  130.         to implement couple of routines, but I get the advantages of b-trees anyway.  In 
  131.         particular, it is not a balanced tree, and makes no effort to do so.  I presume that the
  132.         addresses that are being watched (which are the sort keys), will be fairly random, so
  133.         that the tree will not become seriously overbalanced.  This is reasonable, but in some
  134.         cases I see the tree become overbalanced, depending upon how memory is allocated.
  135.         In any case, the tree never really gets more than about 10-15 levels deep which is still
  136.         two orders of magnitude better than a linear search of the same 1000 or so blocks.  Just
  137.         don't scam this b-tree code assuming that it is rad.  It isn't, but it works for something
  138.         simple like this.  Course you could ask, why is there simple b-tree code here, in this
  139.         trivial tool, but not in the resource manager?  Well, you may well ask.  (one reason is
  140.         that the resource manager allows random sized data, but I will still ask).
  141.         
  142.         Ughh.  I added some dcmdDrawLines here, instead of DebugStr, in the rare case when
  143.         the b-tree may get blown up.  This check is done whenever it is turned on or off, just
  144.         as a consistency check and to be sure the tree is still set up properly and not giving
  145.         bogus info.  This routine will thus run at dcmd time, and if you DebugStr there, it
  146.         will blow away Macsbug giving a 'macsbug caused the exception' error.  Sick.  So I
  147.         changed it to be dcmdDrawLines instead, even though I really want to keep these
  148.         routines from having to know they are part of a dcmd.  I'll think about it.
  149.  
  150.         A couple of things are apparent after using it for awhile.  The b-tree is very unbalanced
  151.         during some use, since the memory manager does a roving up allocation, so the addresses
  152.         tend to be increasing as added, giving an unbalanced tree.  This is OK, but turns the tree
  153.         into a linear list instead.  It actually isn't too bad, so it may not be worth changing, but
  154.         it is worth noting.  If you allocate a lot of blocks, it is possible to get into a bogus tree.
  155.         This almost always happens during an application launch.  For the system heap, memory
  156.         tends to be pretty random, as I want it.  If I'm tracking several hundred blocks in an
  157.         app, it is likely the tree is not balanced, so it will be slower than desired.  Most leak
  158.         check operations are looking in the 10-50 block range, so it's no big deal.
  159.         
  160.  
  161.         The options are: Leaks [On|Off|Dump]
  162.             If you just do Leaks by itself, it will dump the potential leaks in the tree as it exists at that
  163.             point, without changing the on/off state.  This may be helpful for in between tests, but
  164.             is mainly to allow you to get info without having to type the whole thing.  This is
  165.             essentially the same as Leaks Off, but it won't change the watching state. 
  166.             Leaks On will flush the b-tree to a known empty state, and turn the watcher on.  Only
  167.             the header will be displayed to show it went back to all empties.
  168.             Leaks Off will turn the watching mechanism off, saving the tree in that known state.  It
  169.             will also do the dumping operation to display likely leaks, since that is probably what
  170.             you want when you turn it off.  
  171.             Leaks Dump will dump the entire tree (all non-empty elements) so you can see what all
  172.             blocks are being watched if that is helpful.
  173.  
  174.         
  175.         Things to do:
  176.             Is it too restrictive to force the blocks to match including the pc crawls?  If a code block
  177.                 moved around a lot, the pc wouldn't necessarily match, there still would be a leak,
  178.                 but I wouldn't show it. ...  (could do Code+offset)
  179.             Use PatchLink stuff for 7.0. (or does it matter since I patch at Macsbug load time?)
  180.             If you kill the app, I don't see those blocks get marked off.  
  181.                 Later, hook into heap dieing.
  182.             Setup something for the dcmd to use to allocate number of elements in heap?  Some
  183.                 way to make it selectable?
  184.             Selectable way to filter the number of blocks required for a match?  Like not just 3 or more.
  185.             Ideally, I want all the tree knowledge in a b-tree unit instead.  Right now the tree stuff
  186.                 knows a little about the dcmd side, and the dcmd side knows how to drive the tree.
  187.             One thing I sort of want to do is give the address line for every block that looks like a leak,
  188.                 so that you can see them all before the stack crawl.  I don't want more than one stack
  189.                 crawl for a leak though.  This would involve sorting by size instead.  Maybe I can sort
  190.                 of punt this by giving the option of new dump to show only matching elements.
  191.             Maybe I should watch SetHandleSize/PtrSize too, so that the display shows the current
  192.                 block size, instead.   If it is a real leak, it should be the same size later too.
  193. *)
  194.  
  195. {$R-}
  196.  
  197. INTERFACE
  198.  
  199.         USES MemTypes, Resources, Traps, Memory, OSUtils, Events,
  200.                     dcmd;                                            { Macsbug interface routines. }
  201.         
  202.  
  203. CONST
  204.     kOnlyList = 1;                                                { 'Leaks' }
  205.     kTurnOn = 2;                                                { 'Leaks On' }
  206.     kTurnOffNList = 3;                                        { 'Leaks Off' }
  207.     kDumpAll = 4;                                            { 'Leaks Dump' }
  208.  
  209.     kMaxTrackingTableEntries = 500;            { Kinda hard coded, better way?  For # of blocks to watch. }
  210.  
  211.     kHexDigits = '0123456789ABCDEF';        { Digits in base 16, for hex conversion. }
  212.     
  213.     kCrawlArraySize = 8;                                    { Number of stack crawls to do. }
  214.  
  215.  
  216. TYPE
  217.     StackArray = Array [1..kCrawlArraySize] of LongInt;     { Number of stack crawls I do for a call. }
  218.     
  219.     TrackEntryPtr = ^TrackingTableEntry;
  220.     TrackingTableEntry = RECORD
  221.             address:                     LongInt;            { a handle or a pointer, If in emptyQ it is a link. }
  222.             lessThanLink:             TrackEntryPtr;    { queue link to tree whose 'address' is less than this one }
  223.             greaterThanLink:    TrackEntryPtr;    { queue link of tree with 'address' bigger than this one. }
  224.             blockSize:                     LongInt;            { Size of block being tracked. }
  225.             pcStack:                      StackArray;        { stack crawl worth of pcs. }
  226.             tickTime:                     LongInt;            { tickCount when allocated. }
  227.         END;                                                            { The size is multiplied by 500 to give the size of a block in system heap. }
  228.  
  229.     TreeInfo = RECORD                                    { This is the header for the b-trees, used for status info. }
  230.             treeTop:                        TrackEntryPtr;
  231.             treeCount:                 Integer;
  232.             emptyQ:                     TrackEntryPtr;
  233.             emptyCount:             Integer;
  234.                trackActive:                 Boolean;
  235.         END;
  236.  
  237.         { When I'm analyzing the tree for likely leaks, I save off the candidates in an array. }
  238.     TrackTableArray = RECORD
  239.             leakCount: Integer;
  240.             leakEntries: Array [1..10] of TrackingTableEntry;
  241.             leakMatchCount: Array [1..10] of Integer;
  242.         END;
  243.  
  244.         { When I pass back hex numbers on the stack, I want to use small ones. }
  245.     Str8 = String[8];
  246.  
  247.  
  248.  
  249.             { Public declaration for dcmdGlue. Must be in every dcmd. The name cannot be changed. }
  250.         PROCEDURE CommandEntry (paramPtr: dcmdBlockPtr);
  251.  
  252.             { Routine to put another element on b-tree to watch a memory manager address. }
  253.         PROCEDURE AddNewBlock (addressToAdd, sizeToAdd: LongInt; stackToAdd: StackArray;
  254.             VAR treeTop, emptyQ: TrackEntryPtr);
  255.         
  256.             { Routine to forget about a b-tree element watching an address. }
  257.         PROCEDURE KillOldBlock (addressToKill: LongInt; VAR treeTop, emptyQ: TrackEntryPtr);
  258.  
  259.  
  260. IMPLEMENTATION
  261.  
  262.  
  263.         { These globals are used so that I can limit the stack usage during recursion.  They don't
  264.             really have to be globals, but it is a convenience.  I label them with a p, so that you can
  265.             immediately see they are private globals (to this unit), a quality MacApp convention. }
  266.     VAR    pDumpString: Str255;                    { To dump label info, from symbols in code. }
  267.                 pLeakRecord: TrackTableArray;    { Array of likely leaks, to be dumped. }
  268.                 pCountEm: Integer;                        { number of exact matches during analysis. }
  269.                 pTreeInfo: TreeInfo;                        { common tree header from Chain result. }
  270.                 pOptionToDo: Integer;                    { global decisions based on command line parameters. }
  271.                 pCheckElement: TrackEntryPtr;    { during analysis, to avoid it on stack. }
  272.                 pBuffer: Ptr;                                        { Address of buffer allocated in system heap. }
  273.                                 
  274.                 
  275. {---------------------------------------------------------------------------------------------------------------------------------}
  276.  
  277.     { Set the address of NewPtr before I patch the trap.  This is so the assembly interface can 
  278.         find this address again, when it is called as part of a NewPtr trap.  This is required because
  279.         I really need PC-relative addressing in order to be able to get this old address.   All four
  280.         of the routines I patch have the same problem, so I have an interface for each.  The
  281.         asm routine just saves off the address passed in, as a PC-Relative variable.  That way
  282.         when the patch code actually executes it can find the header of the b-tree in order to
  283.         add things to it. }
  284. PROCEDURE  SetOldNewPtr (address: LongInt);  EXTERNAL;
  285. PROCEDURE  SetOldNewHandle (address: LongInt);  EXTERNAL;
  286. PROCEDURE  SetOldDisposPtr (address: LongInt);  EXTERNAL;
  287. PROCEDURE  SetOldDisposHandle (address: LongInt);  EXTERNAL;
  288.  
  289.  
  290.     { The references to the asm routines. }
  291. PROCEDURE WatchNewPtr;    EXTERNAL;
  292. PROCEDURE WatchDisposPtr;    EXTERNAL;
  293. PROCEDURE WatchNewHandle;    EXTERNAL;
  294. PROCEDURE WatchDisposHandle;    EXTERNAL;
  295.  
  296.     
  297.     { When I want to get the TreeInfo, I must get it from the asm side of the world.  It
  298.         has saved the addresses in a PC-Relative way, since it needs them whenever the 
  299.         trap patches get called.  This is the interface to get that info. }
  300. FUNCTION GetTreeTop: TrackEntryPtr;     EXTERNAL;
  301. FUNCTION GetEmptyQ: TrackEntryPtr;     EXTERNAL;
  302. FUNCTION    TrackActive: Boolean;    EXTERNAL;
  303.  
  304.     { When I start up this leak testing universe, I have to set the variables used by the
  305.         assembly patch code.  The tree will be allocated and initialized by this dcmd code,
  306.         and then used by the patch code. }
  307. PROCEDURE SetTreeTop (address: TrackEntryPtr);        EXTERNAL;
  308. PROCEDURE SetEmptyQ (address: TrackEntryPtr);        EXTERNAL;
  309. PROCEDURE SetActive (state: Boolean);        EXTERNAL;
  310.  
  311.         
  312. {---------------------------------------------------------------------------------------------------------------------------------}
  313.     { Another handy routine stolen from MacApp to do the conversion on the dang strings.  I
  314.         only pass back Str8, since that is the maximum length, and stack space is limited in 
  315.         Macsbug, and I don't want to waste it needlessly.  
  316.         Notably, this one handles negative LongInts properly, unlike the one distributed with
  317.         the dcmd samples. }
  318. FUNCTION NumberToHex(decNumber: UNIV LongInt): Str8;
  319.  
  320. VAR    i: Integer;
  321.             hexNumber: Str8;
  322.  
  323. BEGIN
  324.     hexNumber[0] := CHR(8);
  325.     FOR i := 8 DOWNTO 1 DO
  326.         BEGIN
  327.             hexNumber[i] := kHexDigits[BAND(decNumber, 15) + 1];
  328.             decNumber := BSR(decNumber, 4);
  329.         END;
  330.     NumberToHex := hexNumber;
  331. END;
  332.  
  333.  
  334. {---------------------------------------------------------------------------------------------------------------------------------}
  335.     { Zero a TrackingTableEntry, by clearing each field in the record.  This is just a 
  336.         little utility routine.  It is used during Init, and when a block is Killed.  I clear the block
  337.         just to be robust, even though it shouldn't matter what is in the block.  If speed were
  338.         an issue (it isn't) then I would skip this clearing, and just hit the fields that really
  339.         matter, like address.  It makes it easier to see what's going on when debugging it,
  340.         and it helps to prevent inadvertent bugs from blowing it up.  Yes, this type of stuff
  341.         can mask some bugs, but it is more important that it run properly. }
  342. PROCEDURE ZeroTrackEntry (VAR theEntry: TrackEntryPtr);
  343.  
  344. VAR    I: Integer;
  345.  
  346. BEGIN
  347.     WITH theEntry^ DO BEGIN
  348.         address := 0;
  349.         lessThanLink := NIL;
  350.         greaterThanLink := NIL;
  351.         blockSize := 0;
  352.         FOR I := 1 to kCrawlArraySize DO
  353.             pcStack[I] := 0;
  354.         tickTime := 0;
  355.     END;    { With toBeEmptied^ }
  356. END;
  357.  
  358.  
  359. {---------------------------------------------------------------------------------------------------------------------------------}
  360.     { Drive the entire buffer as a series of TrackingTableEntries, clearing each field in the 
  361.         records, and setting up the empty links between records.  This is a brute force way to
  362.         clean them out, but this is a robust way to do it.  It presumes nothing about the buffer,
  363.         except that it has been allocated.  For instance, it doesn't rely on the record structure
  364.         being valid.  It could be blown up because of a bug here, but this will fix it.  This is
  365.         robustness.  Sure, sure, it should never get blown up, but why not be robust instead
  366.         of assuming things will always work properly?  Now set up all the links from one
  367.         'address' to another, to make a linked list of empty q elements.  I'm skipping the last
  368.         entry in the queue (the -1), leaving it NIL to mark the end of the list.}
  369. PROCEDURE InitQ (buffer: Ptr);
  370.  
  371. VAR    thisEntry: TrackEntryPtr;
  372.             I: Integer;
  373.             
  374. BEGIN
  375.     thisEntry := TrackEntryPtr(buffer);
  376.     FOR I := 1 to kMaxTrackingTableEntries-1 DO  BEGIN
  377.         ZeroTrackEntry (thisEntry);
  378.         thisEntry^.address := ORD(thisEntry) + SIZEOF (TrackingTableEntry);
  379.         thisEntry := TrackEntryPtr(thisEntry^.address);
  380.     END;
  381.     ZeroTrackEntry (thisEntry);                                        { zero the last entry too. }
  382. END;
  383.  
  384.  
  385. {---------------------------------------------------------------------------------------------------------------------------------}
  386.     { Check a sub tree recursively to be sure it is valid. 
  387. ;  This routine will drive the entire b-tree in memory, making sure that
  388. ;     it is consistent.  If it finds a problem there is a problem with the b-tree code, and
  389. ;    thus this will break into the debugger.
  390. ;    It will check to be sure that all of the elements in the tree are set up properly, like
  391. ;    having the less than side have an address less than the owning element, and the
  392. ;    same on the greater than side.  This will ensure the tree will not have elements
  393. ;    out of place.  It will check the empty-q to be sure that it is still valid, and that
  394. ;    all of the elements are empty.  While doing these checks it will count the 
  395. ;    number of elements in each queue, making sure that I haven't lost any
  396. ;    elements. }
  397.  
  398. PROCEDURE CheckSubTree (treeElement: TrackEntryPtr; VAR bElementCount: Integer);
  399.  
  400. BEGIN
  401.         { If I have a non-empty less than node, check it out. }
  402.     IF treeElement^.lessThanLink <> NIL THEN BEGIN
  403.     
  404.             { I have another sub-tree, check that element with respect to this one,
  405.                 and if not valid, blow into debugger. }
  406.         IF treeElement^.address <= treeElement^.lessThanLink^.address THEN
  407. (*            DebugStr ('b-tree is fried.  lessThanLink is wrong.');        *)
  408.             dcmdDrawLine (ConCat('b-tree is fried.  lessThanLink is wrong.  ', 
  409.                 NumberToHex (treeElement^.address)));
  410.         
  411.             { I have a cool link on the less than side.  Go ahead and recursively check
  412.                 the subtree on that side. }
  413.         CheckSubTree (treeElement^.lessThanLink, bElementCount);
  414.     END;
  415.     
  416.         { Check the greater than side too, to ensure it is valid. }
  417.     IF treeElement^.greaterThanLink <> NIL THEN BEGIN
  418.     
  419.             { I have another sub-tree, check that element with respect to this one,
  420.                 and if not valid, blow into debugger. }
  421.         IF treeElement^.address >= treeElement^.greaterThanLink^.address Then
  422. (*            DebugStr ('b-tree is fried.  greaterThanLink is wrong.');    *)
  423.             dcmdDrawLine (ConCat('b-tree is fried.  greaterThanLink is wrong.  ', 
  424.                 NumberToHex (treeElement^.address)));
  425.  
  426.             
  427.             { I have a cool link on the greater than side.  Go ahead and recursively check
  428.                 the subtree on that side. }
  429.         CheckSubTree (treeElement^.greaterThanLink, bElementCount);
  430.     END;
  431.     
  432.         { I've checked both sides of this element and it is valid.  Count this as
  433.             a valid element, then fall out of this level of recursion. }
  434.     bElementCount := bElementCount + 1;
  435. END;
  436.  
  437.  
  438. {---------------------------------------------------------------------------------------------------------------------------------}
  439. { The outside level to check the b-tree and empty queue for validity.  This will
  440.     call the recursive routine to check the b-trees, counting elements as it goes. 
  441.     The three queues are passed in, to simplify finding them. }
  442.  
  443. FUNCTION CheckQs (treeTop, emptyQ: TrackEntryPtr; 
  444.         maxElements: Integer; active: Boolean): TreeInfo;
  445.  
  446. VAR        bElementCount:  Integer;
  447.             qWalk: TrackEntryPtr;
  448.             tempInfo: TreeInfo;
  449.  
  450. BEGIN
  451.         { Copy the heads of the queues off, so I can return them later.   The count
  452.             of elements will be set as I count them. }
  453.     tempInfo.treeTop := treeTop;
  454.     tempInfo.emptyQ := emptyQ;
  455.     tempInfo.trackActive := active;
  456.     
  457.     bElementCount := 0;                            { Start element count at zero. }
  458.     
  459.         { Drive the b-tree queue to be sure it is valid.   Start at the top of the tree,
  460.             unless there are no elements. }
  461.     IF treeTop <> NIL  THEN  CheckSubTree (treeTop, bElementCount);
  462.     tempInfo.treeCount := bElementCount;
  463.              
  464.         { If I lived through that, both b-trees are valid.  Now check the empty
  465.             list to be sure that all the links are still valid there.   As long as the 
  466.             empty Q is not completely used up, start at the top and drive each link. }
  467.     qWalk := emptyQ;
  468.     
  469.     IF emptyQ <> NIL THEN
  470.         REPEAT
  471.             IF qWalk^.lessThanLink <> NIL  THEN
  472. (*                DebugStr ('empty queue list is fried.  lessThanLink non-NIL');        *)
  473.                 dcmdDrawLine (ConCat('empty queue list is fried.  lessThanLink non-NIL-- ', 
  474.                     NumberToHex (qWalk^.lessThanLink)));
  475.             IF qWalk^.greaterThanLink <> NIL  THEN
  476. (*                DebugStr ('empty queue list is fried.  greaterThanLink non-NIL');    *)
  477.                 dcmdDrawLine (ConCat('empty queue list is fried.  greaterThanLink non-NIL-- ', 
  478.                     NumberToHex (qWalk^.greaterThanLink)));
  479.                 
  480.             bElementCount := bElementCount + 1;
  481.             qWalk := TrackEntryPtr(qWalk^.address);
  482.         UNTIL qWalk = NIL;
  483.     
  484.         { How ever many I saw there as free needs to be passed back. }
  485.     tempInfo.emptyCount := bElementCount - tempInfo.treeCount;
  486.     
  487.     
  488.         { I've driven the entire list of queue element in the world.  Now if the
  489.             count of elements doesn't jive with what I started, then barf, assuming
  490.             some of them got lost. }
  491.     IF bElementCount < maxElements THEN
  492. (*        DebugStr ('count of elements is off.  lost some');    *)
  493.         dcmdDrawLine (ConCat('count of elements is off.  lost some-  ', 
  494.             NumberToHex (bElementCount)));
  495.     IF bElementCount > maxElements THEN
  496. (*        DebugStr ('count of elements is off.  gained some!');        *)
  497.         dcmdDrawLine (ConCat('count of elements is off.  gained some!  ', 
  498.             NumberToHex (bElementCount)));
  499.         
  500.         { Return the TreeInfo record that gives pertinent tidbits about this system. }
  501.     CheckQs := tempInfo;
  502. END;
  503.  
  504.  
  505. {---------------------------------------------------------------------------------------------------------------------------------}
  506. { ; AddNewBlock will take an address on input, and add it to the b-tree.  It does this
  507. ; by taking an element off of the empty queue list, filling in the fields for the element,
  508. ; then adding it to the b-tree list, by comparing the 'address' fields, to find where it
  509. ; fits in the hierarchy.   On entry, addressToAdd is the address of the block to track.  
  510. ; This is an address in the heap, pointing to the master pointer, or the block itself.  
  511.  
  512. The stackToAdd is an array of kCrawlArraySize elements that have the return addresses from the stack
  513. crawl if they were valid.  These were validated before coming here, and if they weren't
  514. valid, they are nil to mark them as unused.
  515.  
  516.  Both the treeTop and top of the empties list will be modified by this routine, since
  517.  it swaps an element out of the empty list into the b-tree as in use. }
  518.  
  519. PROCEDURE AddNewBlock (addressToAdd, sizeToAdd: LongInt; stackToAdd: StackArray;
  520.     VAR treeTop, emptyQ: TrackEntryPtr);
  521.  
  522. VAR    searchElement: TrackEntryPtr;            { scratch element pointer. }
  523.             ownerElement: TrackEntryPtr;            { owner of searchElement. }
  524.             freshElement: TrackEntryPtr;            { fresh from empties list. }
  525.             I: Integer;
  526.  
  527. BEGIN
  528.         { Check to see if I have used all the free elements up.  *** perhaps I should just
  529.             turn the tree off, as an assumption that they left it on accidentally?   This is one
  530.             DebugStr I don't change, since this will run at normal time, not in Macsbug. }
  531.     freshElement := emptyQ;
  532.     IF freshElement = NIL  THEN BEGIN
  533.         DebugStr ('Barf, no more empty queue elements!-LeakWatching...');
  534.         Exit (AddNewBlock);
  535.     END;
  536.         
  537.         { Pull top element off the empties list, and relink that list so that the next
  538.             element in line is up for use. }
  539.     emptyQ := TrackEntryPtr(freshElement^.address);
  540.     
  541.         { This will be a leaf node, clear the links.  Set up the address to watch. }
  542.     WITH freshElement^ DO BEGIN
  543.         lessThanLink := NIL;
  544.         greaterThanLink := NIL;
  545.         address := addressToAdd;
  546.         blockSize := sizeToAdd;
  547.         tickTime := TickCount;
  548.         
  549.         FOR I := 1 to kCrawlArraySize DO
  550.             pcStack[I] := stackToAdd[I];
  551.     END;        { With freshElement }
  552.     
  553.         { Now drive the b-tree to find the location to add the block at.  The tree may
  554.             be empty, so check that first. }
  555.     searchElement := treeTop;
  556.     IF searchElement = NIL THEN 
  557.         treeTop := freshElement                            { New top of tree. }
  558.     ELSE  BEGIN
  559.             { Loop through the b-tree to find the location that this block should be
  560.                 added at.  This will be a node which is NIL, which I can fill in
  561.                 with the freshElement. }
  562.         REPEAT
  563.             ownerElement := searchElement;            { moved to a new non-nil one. }
  564.             IF addressToAdd < searchElement^.address THEN
  565.                 searchElement := searchElement^.lessThanLink
  566.             ELSE
  567.                 searchElement := searchElement^.greaterThanLink
  568.         UNTIL searchElement = NIL;
  569.     
  570.             { Now add this fresh dude to the b-tree list. }
  571.         IF freshElement^.address < ownerElement^.address THEN
  572.             ownerElement^.lessThanLink := freshElement
  573.         ELSE
  574.             ownerElement^.greaterThanLink := freshElement
  575.     END;        { Else.  not new top of tree. }
  576. END;
  577.  
  578.  
  579. {---------------------------------------------------------------------------------------------------------------------------------}
  580. { Tree deletion.  This is the main reason to use Pascal instead of assembly.  This
  581.     routine is much easier to understand in high level. 
  582.  
  583. ; KillOldBlock is the routine to have us forget about a block that I had previously
  584. ; been watching.  When a block is disposed out of the heap, I have to forget about
  585. ; it, since I only want to keep track of things that are currently in use by the system.
  586. ; On entry to Kill, I have addressToKill as the address of the block to be removed from 
  587. ; the b-tree based list.  I will use that address to drive the tree looking for the b-tree
  588. ; element that is tracking that block in the heap.  If I cannot find it, I let it go,
  589. ; presuming it was allocated before I was watching the blocks.  
  590.  
  591. The treeTop and emptyQ are VAR so that they can be changed if necessary to 
  592. handle the emptying of either queue.
  593.  
  594. This was adapted from an algorithm in Sedgewick.  I tried to follow his code for
  595. the most part, to minimize changes that might introduce bugs.   Here is his code,
  596. copied out straight, if it helps (my comments):
  597.  
  598. ; This is relatively hairy, so just to help, here is the code from Sedgewick that 
  599. ; demonstrates the remove of an element in pascal.  t is the element to kill,
  600. ; x is the head of the tree.
  601. ;    procedure    treeDelete (t, x: Link);
  602. ;        var p, c : Link;
  603. ;        begin
  604. ;            repeat
  605. ;                p := x;
  606. ;                if t^.key < x^.key then x := x^.l else x := x^.r;
  607. ;            until x = t;
  608. ;            if t^.r = z then x := x^.l
  609. ;            else  if t^.r^.l = z then 
  610. ;                begin  x := x^.r; x^.l := t^.l;  end
  611. ;            else
  612. ;                begin
  613. ;                    c := x^.r;  while c^.l^.l <> z do c := c^.l;
  614. ;                    x := c^.l; c^.l := x^.r;
  615. ;                    x^.l := t^.l; x^.r := t^.r;
  616. ;                end;
  617. ;            if t^.key < p^.key then p^.l := x else p^.r := x;
  618. ;        end;
  619. ;
  620. ;     Thank Sedgewick for the lame variable names.
  621. }
  622.  
  623. PROCEDURE KillOldBlock (addressToKill: LongInt; VAR treeTop, emptyQ: TrackEntryPtr);
  624.  
  625. VAR        ownerElement: TrackEntryPtr;            { The owner of the element to be killed. }
  626.             searchElement:    TrackEntryPtr;            { Used as a scratch element pointer. }
  627.             toBeEmptied: TrackEntryPtr;                { when adding back to empties list. }
  628.             subTreeOwner: TrackEntryPtr;            { to move a leaf node to replace killed. }
  629.             I: Integer;
  630.             
  631. BEGIN
  632.         { Bail out of here if the tree is empty, nothing to remove. }
  633.     IF treeTop = NIL  THEN Exit(KillOldBlock);
  634.     
  635.     searchElement := treeTop;
  636.     ownerElement := NIL;
  637.     
  638.         { Find the element that is tracking the addressToKill. }
  639.     WHILE addressToKill <> searchElement^.address DO BEGIN
  640.         ownerElement := searchElement;            { New searcher, means new owner. }
  641.         IF addressToKill < searchElement^.address THEN 
  642.             searchElement := searchElement^.lessThanLink
  643.         ELSE 
  644.             searchElement := searchElement^.greaterThanLink;
  645.         
  646.             { If I didn't find it before running off the end of a leaf, bail out. }
  647.         IF searchElement = NIL THEN Exit(KillOldBlock);
  648.     END;
  649.     
  650. {
  651. ; When I have found a b-tree element that has a matching 'address' field, I have
  652. ; found the element.  Remove it from the tree and put it back into the free element
  653. ; list.  This means getting out the book to see how this works.  The basic idea is to
  654. ; look at both the lessThan and greaterThan links to see if they have they have any
  655. ; subtrees, and if not, just move them in, setting the links in the owner element.  If
  656. ; both sides have subtrees, then I want to drive the lessThan side to find the element
  657. ; that is out at the end of that subtree, then I will move it up into the current location.
  658. ; This takes a leaf node, and moves it further up in the tree, but keeps the tree sorted
  659. ; by address the way I need it.  For a complete discussion, see Sedgewick.
  660. ; When the block has been found the ownerElement will be set to the parent b-tree
  661. ; element used, and bTreeBlock will be the actual element that matches.
  662.  
  663.          Now the searchElement is the guy to be removed from the list.  The
  664.         ownerElement is the current owner of that element. }
  665.  
  666.     toBeEmptied := searchElement;
  667.  
  668.         { The first case is if the element being killed has no greaterThanLink.  If not,
  669.             I can just move the lessThanLink from the toBeEmptied into the 
  670.             ownerElement's lessThanLink.  The idea is that if one side has no
  671.             subTree, then I can just move the subtree into the old spot. }
  672.     IF toBeEmptied^.greaterThanLink = NIL THEN 
  673.         searchElement := toBeEmptied^.lessThanLink    { grab pointer to subtree. }  
  674.     ELSE 
  675.             { Second case is if the descendant of the greaterThanLink has an empty
  676.                 lessThanLink.  This means I can just move the element up one by
  677.                 modifying it's greaterThanLink as well as the ownerElement's link. }
  678.         IF toBeEmptied^.greaterThanLink^.lessThanLink = NIL THEN 
  679.             BEGIN 
  680.                 searchElement := toBeEmptied^.greaterThanLink; 
  681.                 searchElement^.lessThanLink := toBeEmptied^.lessThanLink;  
  682.             END
  683.         ELSE
  684.                 { Otherwise I have the hardest case of having both subtrees in use. 
  685.                     I need to drive down the subtree to the smallest node, and move
  686.                     that node up to the current position, to replace the toBeEmptied. }
  687.             BEGIN
  688.                 subTreeOwner := toBeEmptied^.greaterThanLink;
  689.                 WHILE  subTreeOwner^.lessThanLink^.lessThanLink <> NIL DO
  690.                     subTreeOwner := subTreeOwner^.lessThanLink;
  691.                 searchElement := subTreeOwner^.lessThanLink; 
  692.                 subTreeOwner^.lessThanLink := searchElement^.greaterThanLink;
  693.                 searchElement^.lessThanLink := toBeEmptied^.lessThanLink; 
  694.                 searchElement^.greaterThanLink := toBeEmptied^.greaterThanLink;
  695.             END;
  696.     
  697.         { If the ownerElement is NIL, I am removing the top of the tree, so I have
  698.             a new treetop. }
  699.     IF ownerElement = NIL THEN
  700.         treeTop := searchElement
  701.     ELSE
  702.             { Decide which side of the tree to add to. }    
  703.         IF toBeEmptied^.address < ownerElement^.address THEN
  704.             ownerElement^.lessThanLink := searchElement 
  705.         ELSE
  706.             ownerElement^.greaterThanLink := searchElement;
  707.             
  708.         { Now the element has been removed from the tree.  Add it back into the
  709.             empties list so it is available for use.  This resets the top of the empties list. }
  710.     ZeroTrackEntry (toBeEmptied);
  711.     
  712.     toBeEmptied^.address := ORD4(emptyQ);
  713.     emptyQ := toBeEmptied;
  714. END;
  715.  
  716.  
  717. {---------------------------------------------------------------------------------------------------------------------------------}
  718.     { This is an init routine that sets up the trap patches, and creates and inits the block in
  719.         the system heap that is used to store the records that track each block I see go by.  This
  720.         is a hard-coded tracking size, which is bad.   This part uses the toolbox, which is a bad
  721.         idea for dcmds to do.   If I can't get space for the buffer, I won't install the patches,
  722.         and I'll beep to let them know.   Just added the     dcmdSwapWorlds to make it work
  723.         with TMon Pro. }
  724. PROCEDURE CreateLeakWatcher;
  725.  
  726. BEGIN
  727.         { Before I watch anything, the tree must be empty, and turned off by default. }
  728.     SetTreeTop (NIL);
  729.     SetActive (FALSE);
  730.  
  731.         { I need to create the big buffer that holds all the elements, but they initially will
  732.             all be zeroed, and chained together into the emptyQ list.  If I can't get it, beep. }
  733.     pBuffer := NewPtrSys (kMaxTrackingTableEntries*SIZEOF(TrackingTableEntry));
  734.     IF pBuffer = NIL  THEN  BEGIN
  735.         SysBeep (5);
  736.         Exit (CreateLeakWatcher);                        { Skip out, avoiding trap patches. }
  737.     END;
  738.     
  739.         { Got the dang buffer.  Clear every record in the buffer, and reset all the linked list
  740.             address pointers. The tree will thus be empty, and the emptyQ will have all the
  741.             records. }
  742.     InitQ (pBuffer);
  743.     
  744.         { The queue is set up as a linked list of empty elements.  Tell the asm side where it starts. }
  745.     SetEmptyQ (TrackEntryPtr(pBuffer));
  746.  
  747.         { Patch the traps....  These are being patched in the world, not in the debugger world. }
  748.         { Switch over to the real world, in case the debugger does world swaps.  TMon Pro.}
  749.     dcmdSwapWorlds;
  750.  
  751.         { Use NGetTrapAddress since it is always safer on current machines.  Take the result
  752.             it gives me, and save it off in asm land, for future reference.  Then, move in the
  753.             new address of the routine, my asm glue, with watching junk. }
  754.     SetOldNewPtr (NGetTrapAddress(_NewPtr, OSTrap));
  755.     NSetTrapAddress(ORD(@WatchNewPtr), _NewPtr, OSTrap);    
  756.     
  757.         { Do DisposPtr }
  758.     SetOldDisposPtr (NGetTrapAddress(_DisposPtr, OSTrap));
  759.     NSetTrapAddress(ORD(@WatchDisposPtr), _DisposPtr, OSTrap);
  760.  
  761.         { Do the obvious NewHandle dude too. }
  762.     SetOldNewHandle(NGetTrapAddress(_NewHandle, OSTrap));
  763.     NSetTrapAddress(ORD(@WatchNewHandle), _NewHandle, OSTrap);    
  764.     
  765.         { Do DisposHandle, too. }
  766.     SetOldDisposHandle (NGetTrapAddress(_DisposHandle, OSTrap));
  767.     NSetTrapAddress(ORD(@WatchDisposHandle), _DisposHandle, OSTrap);
  768.  
  769.         { Switch back to debugger world. }
  770. (*    dcmdSwapWorlds; *)
  771. END;        { CreateLeakWatcher }
  772.  
  773.  
  774. {---------------------------------------------------------------------------------------------------------------------------------}
  775.     { Just a handy place to dump out the info about an element.  This has been changed to dump using the
  776.         Macsbug call backs instead, and to use the NumberToHex routine for the numbers.  Do a stack crawl, using
  777.         the address given, trying to see if there is a symbol associated.   I dump them out from the highest to lowest
  778.         to match the StackCrawl that Macsbug uses.  Also, since some are set to Nil when the stack crawler doesn't
  779.         have a valid address, I look for that, and skip the dump if the pc address was not valid. 
  780.         I added the matchCount to give the info about the number of blocks that match this one, but have
  781.         a different address. }
  782. PROCEDURE PrintElement (element: TrackEntryPtr; matchCount: Integer);
  783.  
  784. VAR    I: Integer;
  785.  
  786. BEGIN
  787.     WITH element^ DO
  788.         BEGIN
  789.             dcmdDrawLine(ConCat('address: ', NumberToHex(address), '  size: ', NumberToHex(blockSize), 
  790.                 '   time: ', NumberToHex(tickTime), '    matches: ', NumberToHex(matchCount)));
  791.             FOR I := kCrawlArraySize DownTo 1 DO 
  792.                 IF pcStack[I] <> 0 THEN BEGIN        
  793.                     dcmdGetNameAndOffset (pcStack[I], pDumpString);
  794.                     dcmdDrawLine(ConCat('    ', NumberToHex(pcStack[I]), ':   ', pDumpString));
  795.                 END;
  796.         END;
  797. END;
  798.  
  799.  
  800. {---------------------------------------------------------------------------------------------------------------------------------}
  801.     { A more rough printout of the elements, that is used for the dump operation.  The stack crawl
  802.         during a full dump seemed a bit much, so this gives you the numbers, but without symbols.
  803.         The most interesting info is the block address, so I do that for each block, of course. }
  804. PROCEDURE PrintRaw (element: TrackEntryPtr);
  805.  
  806. BEGIN
  807.     WITH element^ DO
  808.         BEGIN
  809.             dcmdDrawLine(ConCat('address: ', NumberToHex(address), '  size: ', NumberToHex(blockSize), '   time: ', NumberToHex(tickTime)));
  810.             dcmdDrawLine(ConCat('    pc1: ', NumberToHex(pcStack[1]), '   pc2: ', NumberToHex(pcStack[2]), '   pc3: ', NumberToHex(pcStack[3]), '   pc4: ', NumberToHex(pcStack[4])));
  811.             dcmdDrawLine(ConCat('    pc5: ', NumberToHex(pcStack[5]), '   pc6: ', NumberToHex(pcStack[6]), '   pc7: ', NumberToHex(pcStack[7]), '   pc8: ', NumberToHex(pcStack[8])));
  812.         END;
  813. END;
  814.  
  815.  
  816. {---------------------------------------------------------------------------------------------------------------------------------}
  817.     { Recursively dump the tree from the lowest address on up.  Since I'm dumping the entire
  818.         tree, and not just likely leaks, I'll dump it out in a more raw format, without doing
  819.         the stack crawl via labels.  This is to take up less space visually in the scrolling area.  I 
  820.         don't really expect anyone to use this option that much, although it does give you the
  821.         addresses of all the blocks currently being tracked. }
  822. PROCEDURE DumpTree (element: TrackEntryPtr);
  823.  
  824. BEGIN
  825.     IF element^.lessThanLink <> NIL THEN
  826.         DumpTree(element^.lessThanLink);
  827.  
  828.     PrintRaw(element);                                                         { do it after driving smallest links. }
  829.  
  830.     IF element^.greaterThanLink <> NIL THEN
  831.         DumpTree(element^.greaterThanLink);
  832. END;
  833.  
  834.  
  835. {---------------------------------------------------------------------------------------------------------------------------------}
  836.     { Minor routine to see if the two elements actually match in size as well as all the stack crawl
  837.         entries in each element.  Don't care about Time, and certainly not the address. 
  838.         I do the size first, since it is most likely to not match, then backwards through the 
  839.         crawl, since the topmost number is most likely not to match.  (a minor optimization) }
  840. FUNCTION ElementMatch (el1, el2: TrackEntryPtr): Boolean;
  841.     
  842. VAR    I: Integer;
  843.  
  844. BEGIN
  845.     ElementMatch := FALSE;                                        { Assume they don't match, so I can jump out. }
  846.     
  847.     IF el1^.blockSize <> el2^.blockSize THEN Exit (ElementMatch);
  848.     FOR I := kCrawlArraySize DownTo 1 DO
  849.         IF el1^.pcStack[I] <> el2^.pcStack[I] THEN Exit (ElementMatch);
  850.         
  851.     ElementMatch := TRUE;                                        { Made it through, must match. }
  852. END;
  853.         
  854.  
  855. {---------------------------------------------------------------------------------------------------------------------------------}
  856.     { Add an element to the array of known duplicates.  If it already exists in the array,
  857.         skip it.   If I already have 10 elements in the array, skip it, since this is leak city.
  858.         Once they fix a few leaks, then try again, you'll see more.  I limit it to 10 since
  859.         Macsbug has a limited stack, and don't want to burn up too much for elements
  860.         I may never use.   All this code is recursive, so I gotta keep the stack small
  861.         as I can. 
  862.         By checking for an exact match here, I can avoid adding extra elements to the
  863.         list, and using this list I can just dump these elements, giving the stack crawl
  864.         of a single block, rather than each one that matches.  The user thus just sees
  865.         a single leaking stack crawl, with one of the blocks.  If they want to see all 
  866.         the blocks, they can do a dump array command instead. 
  867.         As part of the adding, I'm adding the pCountEm so I can dump that tidbit of info
  868.         along with the elements.  This is the number of entries in the b-tree that match
  869.         this element, which is >= 3, and the actual number may be helpful.  If you want
  870.         to see all the blocks, do a dump instead, and look for the size manually. }
  871. PROCEDURE AddToArray (elementToAdd: TrackEntryPtr);
  872.  
  873. VAR    I: Integer;
  874.  
  875. BEGIN
  876.     WITH pLeakRecord DO BEGIN
  877.         IF leakCount = 10 THEN 
  878.             Exit (AddToArray);                                                { If I'm full up, skip it. }
  879.         
  880.         FOR I := 1 TO leakCount DO
  881.             IF  ElementMatch (elementToAdd, @leakEntries[I])  THEN
  882.                 Exit (AddToArray);                                            { once it's been found, no need to scan them all. }
  883.             
  884.             { No matching element in the leakEntries array yet, so go ahead and add it.  (copy all fields over) }
  885.         leakCount := leakCount + 1;
  886.         leakEntries[leakCount] := elementToAdd^;
  887.         leakMatchCount[leakCount] := pCountEm;        { number of matching elements in tree. }
  888.     END;    { With leakRecord }
  889. END;
  890.  
  891.  
  892. {---------------------------------------------------------------------------------------------------------------------------------}
  893.     { Given an element to examine, drive the tree looking for other blocks that match. 
  894.         I drive the whole tree now, but it should be reasonable to skip out after pCountEm goes over
  895.         3, since it is likely to be a leak for the current element.  This would complicate a recursive
  896.         routine, which goes against my grain.  The pCountEm parameter is passed as a global, so
  897.         I don't have to burn up stack for it.   Since this is the second loop of a doubly nested
  898.         recursive treewalk, I use the pCheckElement as the current element being examined
  899.         from the outer loop.  It changes for each iteration of the outside loop, while I drive
  900.         the entire tree again in this loop.  This use of a global is a little sick, but allows me to
  901.         trim the amount of stuff on the stack as I scan for matching elements. 
  902.         
  903.         I've also added a sick check to see if the element^.address is a resource handle or not.
  904.         If it is, this element cannot be a leak yet, since it is being used by the resource manager.
  905.         I was seeing a number of blocks go by that were resource handles, that happened to have
  906.         the same size, and the same stack crawl.  They aren't leaks, so this change is to get rid
  907.         of those false alarms.  I check to see if the element itself is a match first, as a minor
  908.         optimization to avoid a lot of resource map driving.  The short circuit & will bail
  909.         if it's not a match.  Notably this is using the Resource Manager at interrupt time.
  910.         The user might very well have dropped into Macsbug at a strange place.  This is
  911.         probably not a big deal, since all it has to do is drive a block in the heap, looking 
  912.         through the resource map for a match.  I don't want to do that same driving, since
  913.         any code here would have the same problems as RsrcMapEntry.  If you ever see
  914.         any problems with this, I would be very interested to know. 
  915.         The RsrcMapEntry will return -1 if it doesn't find one, not zero as documented. }
  916. PROCEDURE CountMatchingSize (element: TrackEntryPtr);
  917.  
  918. BEGIN
  919.     IF ElementMatch(element, pCheckElement) & (RsrcMapEntry(Handle(element^.address)) = -1) THEN
  920.         pCountEm := pCountEm + 1;                                { Up the count before recursing. }
  921.  
  922.     IF element^.lessThanLink <> NIL THEN            { If I have a link, go there too. }
  923.         CountMatchingSize(element^.lessThanLink);
  924.     IF element^.greaterThanLink <> NIL THEN        { Recursively drive the right link too. }
  925.         CountMatchingSize(element^.greaterThanLink);
  926. END;
  927.  
  928.  
  929. {---------------------------------------------------------------------------------------------------------------------------------}
  930.     { This guy will drive the entire tree in memory, and for each element, it
  931.         will do the CountMatchingSize procedure.  If enough are found (>=3) then I'll 
  932.         print one out later.  If they aren't found, then I just go on to the next element and
  933.         see if any others in the tree match it.  This is thus a two level recursive system to
  934.         find any blocks that have the same size.   You can think of it as being two nested loops,
  935.         the outside driving each element of the tree, and the inside one driving each element
  936.         in the tree, too.   Any elements that appear multiple times (size, stack crawl match)
  937.         I'll add to the array of leaks for later display.  This guy gets passed the treetop to
  938.         start it up.  Careful of the stack usage here, I'm pushing 4 bytes for each recursive
  939.         call here, and 4 bytes for each recursive call of CountMatchingSize.  For a typical b-Tree
  940.         this won't be a problem, since it will only be 10 or so levels at the deepest.  Macsbug
  941.         has a gutless 1K stack though, so it is risky business. }
  942. PROCEDURE DriveTreeForMatch (element: TrackEntryPtr);
  943.  
  944. BEGIN
  945.     pCountEm := 0;
  946.  
  947.         { Start at the treetop again, and see how many match.  Set the global pCheckElement to
  948.             be the current element, since it won't change over the entire invocation of the
  949.             CountMatchingSize. }
  950.     pCheckElement := element;
  951.     CountMatchingSize(pTreeInfo.treeTop);
  952.  
  953.         { If this element is duplicated 3 or more times, save it off in the pLeakRecord. }
  954.     IF pCountEm >= 3 THEN  AddToArray(element);
  955.  
  956.         { Now I'm done with that element, recursively drive each element in the tree
  957.             that was passed in; and thus I'll drive any subtrees. }
  958.     IF element^.lessThanLink <> NIL THEN            { If I have a link, go there too. }
  959.         DriveTreeForMatch(element^.lessThanLink);
  960.     IF element^.greaterThanLink <> NIL THEN        { Recursively drive the right link too. }
  961.         DriveTreeForMatch(element^.greaterThanLink);
  962. END;
  963.  
  964.  
  965. {---------------------------------------------------------------------------------------------------------------------------------}
  966.     { Drive the tree trying to find the likely candidate for a leak. 
  967.         Now the tree is available, drive the tree looking for duplicate blocks.  This is rather
  968.         loose, and a duplicate is considered to be repeated 3 or more times as having the same
  969.         size and stack crawl.  The operation is presumed to have been run 3 or more times, to 
  970.         duplicate a leaked block 3 or more times.   I use the global variable pTreeInfo in order
  971.         to find the top of the b-tree for analysis.  The pLeakRecord is used to keep track of likely
  972.         leaks, and is global too.   (These are globals to avoid some stack usage, not because I
  973.         think globals are a hot idea.  With a 1K stack in Macsbug, and recursive routines, I'm
  974.         going to extremes.) }
  975. PROCEDURE  AnalyzeTree;
  976.  
  977. VAR    I: Integer;
  978.  
  979. BEGIN
  980.     pLeakRecord.leakCount := 0;
  981.     
  982.         { If the tree is non-empty, drive every element in it, trying to find other elements
  983.             that have the same info (blockSize, stackCrawl).  I pass treeTop from the global here,
  984.             but it has to be stack based for the recursive use above. }
  985.     IF pTreeInfo.treeTop <> NIL THEN DriveTreeForMatch(pTreeInfo.treeTop);
  986.  
  987.         { For every block in the seen list, dump it out as the cool info they need to know.  This
  988.             list has no duplicates, so they only get one leak for each element dumped.  If there were
  989.             no leaks, the leakCount is zero, and I don't do this loop at all. }
  990.     FOR I := 1 TO pLeakRecord.leakCount DO
  991.         BEGIN
  992.             dcmdScroll;                                                            { Put in blank line. }
  993.             PrintElement(@pLeakRecord.leakEntries[I], pLeakRecord.leakMatchCount[I]);
  994.         END;
  995. END;
  996.  
  997.  
  998. {---------------------------------------------------------------------------------------------------------------------------------}
  999.     { Dump out the tree info, like the number of elements in use.   This is sort of marginally useful,
  1000.         since you can see how many block are being tracked currently; but the main reason to show it
  1001.         is to give the calling human feedback that something actually happened.  In a case where there
  1002.         were no leaks, this is all you would see (which is preferable to not showing anything).  I also
  1003.         use the global pTreeInfo, to be consistent with the other routines, even though the stack
  1004.         usage isn't really a concern for this routine. }
  1005. PROCEDURE DumpHeaders;
  1006.  
  1007. BEGIN
  1008.     WITH pTreeInfo DO
  1009.         BEGIN
  1010.             IF pTreeInfo.trackActive THEN dcmdDrawLine ('ON:  ')
  1011.             ELSE dcmdDrawLine ('OFF: ');
  1012.             
  1013.                 { Write out:   ' top of tree:  00042133  with 00000500 elements.' }
  1014.             dcmdDrawString (ConCat (' top of tree:', NumberToHex (ORD(treeTop)), '  with ', NumberToHex (treeCount), ' elements.'));
  1015.  
  1016.                 { Write out:   '  empty list:  00042133  with 00000500 elements.' }
  1017.             dcmdDrawLine (ConCat ('       empty list:', NumberToHex (ORD(emptyQ)), '  with ', NumberToHex (emptyCount), ' elements.'));
  1018.         END;
  1019. END;
  1020.  
  1021.     
  1022.     { Get the TreeInfo, and check the b-tree for consistency.  *** make it pointer. }
  1023. FUNCTION GetTreeInfo: TreeInfo;
  1024. BEGIN
  1025.     GetTreeInfo := CheckQs (GetTreeTop, GetEmptyQ,  kMaxTrackingTableEntries, TrackActive);
  1026. END;
  1027.  
  1028.  
  1029. {---------------------------------------------------------------------------------------------------------------------------------}
  1030.     { The top of the dump info food chain.  This guy will dump information out, after driving
  1031.         the tree numerous times.   It will call the b-tree code via asm interface in order to get the magic
  1032.         info of the tree header, so I can drive the tree at will, looking for matching blocks, dumping
  1033.         each block to the output, and so on.  Also, the magic interface is turned on or
  1034.         off, here.  I've passed the pOptionToDo as a global here, going to extremes to avoid using
  1035.         more of the stack than needed. }
  1036. PROCEDURE     DumpLeakBlocks;
  1037.     
  1038. BEGIN
  1039.         dcmdScroll;                                                                { bump up a line in the display. }
  1040.  
  1041.             { Now decide what to do, based on the optionToDo. }
  1042.     CASE pOptionToDo OF
  1043.     
  1044.         kOnlyList:                                                                    { 'Leaks' }
  1045.             BEGIN
  1046.                 pTreeInfo := GetTreeInfo;
  1047.                 DumpHeaders;
  1048.                 AnalyzeTree;
  1049.             END;
  1050.             
  1051.         kTurnOn:                                                                    { 'Leaks On' }
  1052.             BEGIN
  1053.                 SetActive (TRUE);
  1054.                 InitQ (pBuffer);                                                { Clear the buffer, reset the tree and emptyQ. }
  1055.                 SetEmptyQ (TrackEntryPtr(pBuffer));
  1056.                 SetTreeTop (NIL);
  1057.                 pTreeInfo := GetTreeInfo;
  1058.                 DumpHeaders;
  1059.             END;
  1060.                 
  1061.         kTurnOffNList:                                                        { 'Leaks Off' }
  1062.             BEGIN
  1063.                 SetActive (FALSE);
  1064.                 pTreeInfo := GetTreeInfo;
  1065.                 DumpHeaders;
  1066.                 AnalyzeTree;
  1067.             END;
  1068.             
  1069.         kDumpAll:                                                                { 'Leaks Dump' }
  1070.             BEGIN
  1071.                 pTreeInfo := GetTreeInfo;
  1072.                 DumpHeaders;
  1073.                 IF pTreeInfo.treeTop <> NIL THEN  DumpTree(pTreeInfo.treeTop);
  1074.             END;
  1075.             
  1076.         OTHERWISE  dcmdDrawLine (' Syntax Error');
  1077.     END;        { Case pOptionToDo }
  1078.  
  1079. END;        { DumpLeakBlocks }
  1080.  
  1081.  
  1082.  
  1083. {---------------------------------------------------------------------------------------------------------------------------------}
  1084.     { Well, I stole this routine from MacApp utilities.  I want to lower case the strings so I 
  1085.         don't have case sensitivities.  This will do it, without using the toolbox. }
  1086. PROCEDURE LowerStr255(VAR s: Str255);
  1087.  
  1088. VAR    i:    INTEGER;
  1089.  
  1090. BEGIN
  1091.     FOR i := 1 TO LENGTH(s) DO
  1092.         IF (s[i] IN ['A'..'Z']) THEN
  1093.             s[i] := CHR(Ord(s[i]) + 32)
  1094. END;        { LowerStr255 }
  1095.  
  1096.  
  1097. {---------------------------------------------------------------------------------------------------------------------------------}
  1098. {---------------------------------------------------------------------------------------------------------------------------------}
  1099.  
  1100.     { This fine fellow is the main entry point for the dcmd.  It is the hook by which I get called
  1101.         by MacsBug to do my thing.  It is basically the chance to key off the command line and do
  1102.         what they request.  I'm using pDumpString here, since it's temporarily used to build a 
  1103.         pOptionToDo, and I don't want to waste stack space.  It will be pounded by any of the
  1104.         dump routines, so realize this is sick, and dangerous.  Also realize that a Str255 is
  1105.         one-fourth, countem, one-fourth of the entire Macsbug stack (1K).   I can't afford to
  1106.         waste string space, that is clear. 
  1107.         
  1108.         Change the version number in the help, whenever it is re-released.   This is the only
  1109.         version number in the program. }
  1110. PROCEDURE CommandEntry (paramPtr: DCmdBlockPtr);
  1111.  
  1112. VAR     ch:              CHAR;
  1113.                 
  1114. BEGIN
  1115.     CASE    paramPtr^.request OF
  1116.             { When I'm called to Init, do the init code of installing the trap patches, allocate data block. }
  1117.         dcmdInit: 
  1118.                 CreateLeakWatcher;
  1119.                 
  1120.             { I can get various DoIt commands, so parse out the options.   If I don't get anything,
  1121.                 do the standard dump info.  I lowercase the string so I can avoid any case sensitivity
  1122.                 on options passed in. }
  1123.         dcmdDoIt:        
  1124.             BEGIN                    
  1125.                 ch := dcmdGetNextParameter (pDumpString);
  1126.                 LowerStr255 (pDumpString);
  1127.                 IF pDumpString = '' THEN pOptionToDo := kOnlyList
  1128.                 ELSE  IF pDumpString = 'dump' THEN pOptionToDo := kDumpAll
  1129.                 ELSE  IF pDumpString = 'on' THEN pOptionToDo := kTurnOn
  1130.                 ELSE  IF pDumpString = 'off' THEN pOptionToDo := kTurnOffNList
  1131.                 ELSE    pOptionToDo := -1;
  1132.                 
  1133.                 DumpLeakBlocks;                                        { using pOptionToDo to decide. }
  1134.             END;
  1135.             
  1136.             { Give them the obvious help info. }
  1137.         dcmdHelp:                                                    
  1138.             BEGIN
  1139.                 dcmdDrawLine ('Leaks [On|Off|Dump]');
  1140.                 dcmdDrawLine ('   Stack crawl info about likely memory leaks.  (Version 4)');
  1141.             END;
  1142.     END;        { End of case paramPtr^.request. }
  1143.         
  1144. END;    { CommandEntry }
  1145.  
  1146. END.
  1147.